<?php

namespace App\Utility;

use EasySwoole\EasySwoole\Config;
use EasySwoole\EasySwoole\Logger;
use EasySwoole\HttpClient\HttpClient;
use EasySwoole\Spl\SplString;

class MiniProgram{
    const API_BASE = 'https://api.weixin.qq.com/';
    protected $app;
    protected $apis = [
        'access_token' => self::API_BASE.'cgi-bin/token',       //获取access_token
        'session_key'  => self::API_BASE.'sns/jscode2session?',  //获取小程序session_key
        'send_template_message' => self::API_BASE.'cgi-bin/message/wxopen/template/send',  //推送模板消息
        'send_custom_message' => self::API_BASE.'cgi-bin/message/custom/send',  //发送客服消息
        'template_list' => self::API_BASE.'cgi-bin/wxopen/template/list' //模板消息列表
    ];
    protected $cachePrefix = 'wechat.mini.';
    protected $safeSeconds = 500;
    protected $message = [
        'touser' => '',
        'template_id' => '',
        'page' => '',
        'form_id' => '',
        'data' => [],
        'emphasis_keyword' => '',
    ];
    protected $required = ['touser', 'template_id', 'form_id'];
    function __construct(){
        $this->app = Config::getInstance()->getConf('wechat');
    }

    /**
     * 基本配置
     * @return array
     */
    protected function getConfig(): array{
        return [
            'appid' => $this->app['app_id'],
            'secret' => $this->app['secret'],
        ];
    }

    /**
     *  Get session info by code.
     * @param string $code
     * @return mixed
     */
    public function session(string $code){
        $config = $this->getConfig();
        $config = array_merge(['js_code' => $code,'grant_type'=>'authorization_code'],$config);
        return $this->sendRequest('GET',$this->apis['session_key'].http_build_query($config));

    }

    /**
     * send a http request
     * @param string $method
     * @param string $url
     * @param array $params
     * @return mixed|void
     */
    protected function sendRequest(string $method='GET',string $url='', array $params=[]){
        $request = new HttpClient($url);
        if($method == 'POST'){
            $request->post($params);
        }
        $result = $request->exec();
        $string = new SplString($result->getBody());
        $result = json_decode($string, true);
        return $result;
//        $pos = strpos($url,self::API_BASE);
//        if($this->app['log'] && $pos !== false){
//            if($method == 'POST') $this->log(json_decode($params['body']));
//            if(!isset($params['authorization_code'])) $this->log($this->errorToMsg($result));
//        }
//        return $result;
    }

    /**
     * 获取token
     * @return null
     * @throws \Exception
     */
    public function getToken(){
        $cache = $this->getCache();
        $cacheKey = $this->getCacheKey();
        //先从缓存读取
        if($cache->has($cacheKey)){
            $token = $cache->get($cacheKey);
        }else{
            $token = $this->requestToken();
            //设置token缓存
            $this->setToken($token);
        }
        return $token['access_token'];
    }

    /**
     * 请求token
     * @return mixed|void
     */
    protected function requestToken(){
        $config = $this->getConfig();
        $config = array_merge(['grant_type'=>'client_credential'],$config);
        $params = ['query' => $config];
        return $this->sendRequest('GET',$this->apis['access_token'],$params);
    }

    /**
     * 保存token
     * @param $token
     * @return bool
     * @throws \Exception
     */
    protected function setToken($token){
        $this->getCache()->set($this->getCacheKey(), $token, $token['expires_in']-$this->safeSeconds);
        return true;
    }

    /**
     * 获取模板消息列表
     * @return mixed|void
     * @throws \Exception
     */
    public function getTemplateList(){
        $token = $this->getToken();
        $data = ["offset" => 0, "count" => 20];
        $params = $this->formatMessage($data);
        $result = $this->sendRequest('POST',$this->apis['template_list'].'?access_token='.$token,['body' => $params]);
        if($result['errcode'] == 0){
            return $result['list'];
        }
        return $this->errorToMsg($result);
    }

    /**
     * 发送模板消息
     * @param array $data
     * @return bool|string
     * @throws \Exception
     */
    public function sendTemplateMessage($data){
        $token = $this->getToken();
        $params = array_merge($this->message, $data);
        //检查必须填写的字段
        foreach ($params as $key => $value) {
            if (in_array($key, $this->required, true) && empty($value) && empty($this->message[$key])) {
                throw new \Exception(sprintf('字段: "%s" 内容不能为空!', $key));
            }
            $params[$key] = empty($value) ? $this->message[$key] : $value;
        }
        //格式化模板消息内容
        $params['data'] = $this->formatData($params['data']);
        return $this->sendRequest('POST',$this->apis['send_template_message'].'?access_token='.$token,['body' => json_encode($params)]);
    }

    /**
     * 微信errcode转化
     * @param $code
     * @return string
     */
    protected function errorToMsg($result){
        if(!isset($result['errcode'])) return $result;
        switch ($result['errcode']){
            case 40001:
                $msg = '获取access_token时AppSecret错误，或者access_token无效';
                break;
            case 40002:
                $msg = '不合法的凭证类型';
                break;
            case 40003:
                $msg = '不合法的OpenID';
                break;
            case 40037:
                $msg = 'template_id不正确';
                break;
            case 41028:
                $msg = 'form_id无效或者已过期';
                break;
            case 41029:
                $msg = 'form_id已被使用';
                break;
            case 41030:
                $msg = 'page不正确';
                break;
            case 44002:
                $msg = 'post数据为空';
                break;
            case 45009:
                $msg = '小程序模板消息推送接口调用超过限额（目前默认每个帐号日调用限额为100万）';
                break;
            case 45015:
                $msg = '回复时间超过限制';
                break;
            case 45047:
                $msg = '客服接口下行条数超过上限';
                break;
            case 45086:
                $msg = '无效的data-miniprogram-appid标签，必须填写当前的小程序appid';
                break;
            case 48001:
                $msg = 'API功能未授权，请确认小程序已获得该接口';
                break;
            default:
                $msg = '';
                break;
        }
        $result['chmsg'] = $msg;
        return $result;
    }

    /**
     * 格式化模板消息内容（满足小程序和公众号）
     * @param array $data
     * @return array
     */
    protected function formatData($data){
        $formatted = [];
        foreach ($data as $key => $value) {
            if (is_array($value)) {
                if(isset($value['value'])) {
                    $formatted[$key] = $value;
                    continue;
                }
                if(count($value) >= 2) {
                    $value = [
                        'value' => $value[0],
                        'color' => $value[1],
                    ];
                }
            }else{
                $value = ['value' => $value];
            }
            $formatted[$key] = $value;
        }
        return $formatted;
    }



    /**
     * 获取缓存
     * @return RedisTools
     * @throws \Exception
     */
    protected function getCache(){
        return new RedisTools();
    }

    /**
     * 获取缓存前缀
     * @return string
     */
    protected function getCacheKey(){
        return $this->cachePrefix.$this->app['appid'].'.access.token';
    }

    /**
     * 验证token
     * @param $params
     * @return bool
     */
    public function service($params){
        if($this->checkSignature($params)){
            $echoStr = $params["echostr"];
            return $echoStr;
        }
        return false;
    }

    /**
     * 签名校验
     * @param $params
     * @return bool
     */
    protected function checkSignature($params){
        $signature = $params["signature"];
        $timestamp = $params["timestamp"];
        $nonce = $params["nonce"];
        $token = $this->app['token'];
        $tmpArr = [$token, $timestamp, $nonce];
        sort($tmpArr, SORT_STRING);
        $tmpStr = implode($tmpArr);
        $tmpStr = sha1($tmpStr);
        if($tmpStr == $signature){
            return true;
        }
        return false;
    }

    /**
     * 获取HTTP_RAW_POST_DATA
     * @param $body
     * @return mixed
     */
    public function getRaw($body){
        $content = $body->__toString();
        return json_decode($content, true);
    }

    /**
     * 发送客服消息
     * @param $data
     * @return bool|string
     * @throws \Exception
     */
    public function sendCustomerMessage($data){
        $token = $this->getToken();
        $params = $this->formatMessage($data);
        return $this->sendRequest('POST',$this->apis['send_custom_message'].'?access_token='.$token,['body' => $params]);
    }

    /**
     * 微信接口记录日志
     * @param $data
     * @return bool
     */
    protected function log($data){
        Logger::getInstance()->log(json_encode($data),'wechat');
        return true;
    }

    /**
     * 格式化消息内容
     * @$data['type']  文本消息|图片消息|图片消息|图文消息|视频消息|声音消息|小程序卡片
     * @param $data
     * @return array|string
     */
    protected function formatMessage($data){
        if(isset($data['type']) && $data['type']){
            switch ($data['type']){
                case 'text'://文本消息
                    /**
                     * content内容：文本/a标签
                     * <a href="xxxx"> 跳转链接</a>
                     * <a data-miniprogram-appid="xxxx" data-miniprogram-path="xxxx"> 点击跳小程序</a>
                     * data-miniprogram-appid 项，填写小程序appid，则表示该链接跳转小程序，data-miniprogram-appid必须是当前小程序的appid；
                     * data-miniprogram-path项，填写小程序路径，路径与app.json中保持一致，可带参数；
                     * 对于不支持data-miniprogram-appid 项的客户端版本，如果有herf项，则仍然保持跳href中的链接；
                     */
                    $message = [
                        "touser" => $data['openid'],//用户的 OpenID
                        "msgtype" => 'text',//消息类型
                        "text"  => [
                            'content' => $data['content'],//文本消息内容，可以是a标签
                        ],
                    ];
                    break;
                case 'image'://图片消息
                    $message = [
                        "touser" => $data['openid'],//用户的 OpenID
                        "msgtype" => 'image',//消息类型
                        "image"  => [
                            'media_id' => $data['media_id'],//发送的图片的媒体ID(需要通过接口uploadTempMedia上传图片到微信服务器)
                        ],
                    ];
                    break;
                case 'link'://图文消息
                    $message = [
                        "touser" => $data['openid'],
                        "msgtype" => 'link',
                        "link"  => [
                            'title' => $data['title'],//消息标题
                            'description' => $data['description'],//图文链接消息描述
                            'url' => $data['url'],//图文链接消息被点击后跳转的链接
                            'thumb_url' => $data['thumb_url'],//图文链接消息的图片链接，支持 JPG、PNG 格式，较好的效果为大图 640 X 320，小图 80 X 80
                        ],
                    ];
                    break;
                case 'video'://视频消息
                    break;
                case 'voice'://声音消息
                    break;
                case 'miniprogrampage'://小程序卡片
                    $message = [
                        "touser" => $data['openid'],
                        "msgtype" => 'link',
                        "link"  => [
                            'title' => $data['title'],//消息标题
                            'pagepath' => $data['pagepath'],//小程序的页面路径，跟app.json对齐，支持参数，比如pages/index/index?foo=bar
                            'thumb_media_id' => $data['thumb_media_id'],//小程序消息卡片的封面（需要通过接口uploadTempMedia上传图片到微信服务器）
                        ],
                    ];
                    break;
                default:
                    break;
            }
        }else{
            $message = $data;
        }
        return json_encode($message,JSON_UNESCAPED_UNICODE);
    }
}
